/*  tBookShelf: TextPlugin
    (C) 2007  TvN (Volodymyr Tarasenko), e-mail: tvntsr@yahoo.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


// :TODO: check exception safety


#include <iostream>
#include <libgen.h>
#include "LuaParser.hpp"

using namespace std;

const char* FILE_NAME_VAR ="file_name";
const char* NAME_VAR      ="name";
const char* LINE_VAR      ="line";
const char* TRANSFORM_VAR ="transform";
const char* PROFILE_LIST_VAR="profile_list";
const char* ITEM_VAR      ="item";
const char* NEXT_VAR      ="next";
const char* FONT_VAR      ="font";
const char* STYLE_VAR     ="style";
const char* SIZE_VAR      ="size";
const char* HYPHENATION_VAR="hyphenation";
const char* BEFORE_VAR     ="before";
const char* AFTER_VAR      ="after";
const char* FIRSTLINE_VAR  ="firstline";
const char* SPACING_VAR    ="spacing";
const char* INTERLINESPACE_VAR="interlinespace";
const char* TABS_VAR       ="tabs";
const char* SCIPSPACES_VAR ="scipspaces";
const char* TEXT_STYLES_VAR="text_styles";
const char* PARAGRAPH_VAR  ="paragraph";
const char* NEW_VAR        ="new";

const char* FONT_FILE_VAR  ="font_file";
const char* USE_KERNING_VAR="use_kerning";
const char* ALIGN_VAR      ="align";

const char* LEFT           ="left";
const char* RIGHT          ="right";
const char* CENTER         ="center";
const char* JUSTIFY        ="justify";

const char* FUSE_VAR       ="fuse";


extern "C"
int loadHyphenationDict(lua_State *L)
{
    // We should take away the argument in any case...
    size_t path_len = 0;

    cerr << "Check string" << endl;

    const char *path = luaL_checklstring(L, 1, &path_len);

    if (path == NULL)
    {
        return 0;
    }

    cerr << "Path is ["
         << path
         << "]"
         << endl;

    lua_pushlightuserdata(L, (void *)&loadHyphenationDict);  /* push address */
    lua_gettable(L, LUA_REGISTRYINDEX);  /* retrieve value */

    cerr << "Get manager" << endl;
    HyphenationManager *manager = (HyphenationManager*)lua_touserdata(L, -1);

    cerr << "Got:" << (void*)manager
         << endl;

    if (manager == 0)
    {
        cerr << "NULL value for manager, break" << endl;

        lua_pushstring(L, "incorrect HyphenationManager");
        lua_error(L);
        return 0;
    }

    try{
        cerr << "Open file:" << path << endl;

        manager->open(path);
        cerr << "Successfuly done..." << endl;
    }
    catch(std::exception& err)
    {
        cerr << "Error:" << err.what() << endl;

        lua_pushstring(L, err.what());
        lua_error(L);

        return 0;
    }

    return 0;
}

ConfigParser::ConfigParser(const std::string&  main_cfg, 
                           const char*         file_name,
                           HyphenationManager* hman)
    : LuaInterface(),
      curr_profile("default"), 
      mHyphenationManager(hman)
{
    cerr << "Load manager" << endl;
    lua_pushlightuserdata(L, (void *)&loadHyphenationDict);
    lua_pushlightuserdata(L, (void *)hman);
    lua_settable(L, LUA_REGISTRYINDEX);

    cerr << "Load RegisterHyphenationDict" << endl;
    lua_pushcfunction(L, loadHyphenationDict);
    lua_setglobal(L, "RegisterHyphenationDict");
    cerr << "Continue..." << endl;

    lua_pushstring(L, file_name);

    lua_setglobal(L,  FILE_NAME_VAR);

    try{
        addConfig(main_cfg);

        recursiveConfig(file_name);
    }
    catch(std::exception& err)
    {
        lua_close(L);

        throw;
    }
}

ConfigParser::~ConfigParser() throw()
{}


std::string ConfigParser::getStructName(const char* st)
{
    StackAutoLevel tr(L);
    
    // Get structure
    lua_getglobal(L, st);

    // get name field
    string name;
    if (!getLuaValue(name, NAME_VAR))
    {
        throw std::runtime_error(string(__FUNCTION__)+
            "->Lua error: `object' is not a valid structure");
    }
    return name;
}


int ConfigParser::parseLine(const std::string& line, TextStyle& style)
{
    int need_lines = 0;

    StackAutoLevel tr(L);

    standFunction(curr_profile.c_str(), LINE_VAR);

    // Pass self, arg #1
    lua_getglobal(L, curr_profile.c_str());
    
    // Pass line, arg #2
    lua_pushstring(L, line.c_str());
    
    // -3 +2, execute
    doLuaCall(2,2);

    // Result
    if (!lua_istable(L, -1))
    {
        throw std::runtime_error(string(__FUNCTION__)+
                                 "->Lua error: It is no valid result");
    }
    if (lua_isnumber(L, -2))
    {
        need_lines = (int)lua_tonumber(L, -2);
    }

    if (need_lines == 0)
    {
        if (lua_isnil(L, -1))
        {
            throw std::runtime_error(string(__FUNCTION__)+
                                     "->Lua error: nil is not valid result");
        }
        initTextStyle(style);
    }

    return need_lines;
}

size_t ConfigParser::transformLine(std::string& line)
{
    StackAutoLevel tr(L);
    
    standFunction(curr_profile.c_str(), TRANSFORM_VAR);

    // Pass self, arg #1
    lua_getglobal(L, curr_profile.c_str());

    // Pass line, arg #2
    lua_pushstring(L, line.c_str());

    // -3 +1, execute
    doLuaCall(2, 1);

    size_t len = 0;
    line = lua_tolstring (L, -1, &len);

    return len;
}

void ConfigParser::getRegisteredStyles(std::list<std::string>& data)
{
    StackAutoLevel tr(L);

    std::string name;

    // Get structure
    lua_getglobal(L, TEXT_STYLES_VAR);

    while (!lua_isnil(L, -1))
    {
        lua_getfield(L, -1, ITEM_VAR);

        if (lua_isnil(L, -1))
        {
            lua_pop(L, 1);
            break;
        }

        if (!getLuaValue(name, NAME_VAR))
        {
            throw std::runtime_error(string(__FUNCTION__)+
                                     "->Lua error: Bad style");
        }

        data.push_back(name);

        lua_pop(L, 1);
        
        lua_getfield(L, -1, NEXT_VAR);
        
        lua_remove(L, -2);
    }
}

void ConfigParser::getRegisteredProfiles(std::list<std::string>& data)
{
    StackAutoLevel tr(L);

    std::string name;

    // Get structure
    lua_getglobal(L, PROFILE_LIST_VAR);

    while (!lua_isnil(L, -1))
    {
        lua_getfield(L, -1, ITEM_VAR);

        if (lua_isnil(L, -1))
        {
            lua_pop(L, 1);
            break;
        }

        if (!getLuaValue(name, NAME_VAR))
        {
            throw std::runtime_error(string(__FUNCTION__)+
                                     "->Lua error: Bad style");
        }

        data.push_back(name);

        lua_pop(L, 1);
        
        lua_getfield(L, -1, NEXT_VAR);
        
        lua_remove(L, -2);
    }
}


void ConfigParser::setProfile(const std::string& style)
{
    name_style = getStructName(style.c_str());

    curr_profile = style;
}


void
ConfigParser::initTextStyle(TextStyle& style)
{
    string nm;
    if (!getLuaValue(nm, NAME_VAR))
    {
        throw std::runtime_error(string(__FUNCTION__)+
            "->Lua error: `style type' is not a valid structure");
    }
    
    if (nm == style.name)
    {
        // skip reading data, already initialed
        return;
    }

    style.name = nm;

    getLuaValue(style.font,      FONT_VAR);
    getLuaValue(style.font_file, FONT_FILE_VAR);

    getLuaValue(style.style,     STYLE_VAR);
    getLuaValue(style.size,      SIZE_VAR);
 
    getLuaValue(style.before,    BEFORE_VAR);
    getLuaValue(style.after,     AFTER_VAR );
    getLuaValue(style.firstline, FIRSTLINE_VAR);
    getLuaValue(style.spacing,   SPACING_VAR);
    getLuaValue(style.interlinespace,   INTERLINESPACE_VAR);
    getLuaValue(style.tabs,      TABS_VAR);
    getLuaValue(style.scipspaces,SCIPSPACES_VAR);
    string h;
    if(getLuaValue(h,  HYPHENATION_VAR))
    {
        style.hyphenation = strcasecmp(h.c_str(), "yes") == 0;
    }
    else
    {
        style.hyphenation = false;
    }

    if(getLuaValue(h, USE_KERNING_VAR))
    {
        style.use_kerning = strcasecmp(h.c_str(), "yes") == 0;
    }
    else
    {
        style.use_kerning = true;
    }
    if(getLuaValue(h, FUSE_VAR))
    {
        style.fuse = strcasecmp(h.c_str(), "yes") == 0;
    }
    else
    {
        style.fuse = true;
    }
    
    if(getLuaValue(h, ALIGN_VAR))
    {
        if (strcasecmp(h.c_str(), LEFT) == 0)
        {
            style.align = Left;
        }
        else if (strcasecmp(h.c_str(), RIGHT) == 0)
        {
            style.align = Right;
        }
        else if (strcasecmp(h.c_str(), CENTER) == 0)
        {
            style.align = Center;
        }
        else if (strcasecmp(h.c_str(), JUSTIFY) == 0)
        {
            style.align = Justify;
        }
        else
            throw std::runtime_error(string(__FUNCTION__)+
                                 "->Lua error: align type is not supported");
    }
    else
    {
        style.align = Justify;
    }
}


void ConfigParser::saveTextStyle(const TextStyle& style)
{
    StackAutoLevel tr(L);

    std::string name;

    // Get structure
    lua_getglobal(L, TEXT_STYLES_VAR);
    
    while (!lua_isnil(L, -1))
    {
        lua_getfield(L, -1, ITEM_VAR);

        if (lua_isnil(L, -1))
        {
            lua_pop(L, 1);
            break;
        }

        if (!getLuaValue(name, NAME_VAR))
        {
            throw std::runtime_error(string(__FUNCTION__)+
                                     "->Lua error: Bad style");
        }
        
        if (name == style.name)
        {
            lua_pop(L, -1); // remove name

            doSaveText(style);

            return;
        }
        
        lua_pop(L, 1);
        
        lua_getfield(L, -1, NEXT_VAR);
        
        lua_remove(L, -2);
    }

    // not found, create new one 
    createText(style);
}

void ConfigParser::createText(const TextStyle& style)
{
    StackAutoLevel tr(L);

    standFunction(PARAGRAPH_VAR, NEW_VAR);

    // Pass self, arg #1
    lua_getglobal(L, PARAGRAPH_VAR);

    // Pass name, arg #2
    lua_pushstring(L, style.name.c_str());

    doLuaCall(2, 1);

    // Result
    if (!lua_istable(L, -1))
    {
        throw std::runtime_error(string(__FUNCTION__)+
                                 "->Lua error: It is no valid response");
    }

    doSaveText(style);
}

void ConfigParser::doSaveText(const TextStyle& style)
{
    lua_pushstring(L, style.font.c_str());
    lua_setfield(L, -2, FONT_VAR);

    lua_pushstring(L, style.style.c_str());
    lua_setfield(L, -2, STYLE_VAR);

    lua_pushinteger(L, style.size);
    lua_setfield(L, -2, SIZE_VAR);
    
    lua_pushstring(L, style.hyphenation ? "yes" : "no");
    lua_setfield(L, -2, HYPHENATION_VAR);

    lua_pushinteger(L, style.before);
    lua_setfield(L, -2, BEFORE_VAR);
    
    lua_pushinteger(L, style.after);
    lua_setfield(L, -2, AFTER_VAR);

    lua_pushinteger(L, style.firstline);
    lua_setfield(L, -2, FIRSTLINE_VAR);

    lua_pushinteger(L, style.spacing);
    lua_setfield(L, -2, SPACING_VAR);
    
    lua_pushinteger(L, style.tabs);
    lua_setfield(L, -2, TABS_VAR);
}


// void wcharToChar(char** dest, const wchar_t* data)
// {
//     mbstate_t ps;

//     const wchar_t** src = &data;

//     size_t sz = wcslen(data) * sizeof(std::wstring::value_type) + 1;

//     char* tmp_name = new char[sz];

//     size_t ret = wcsrtombs(tmp_name, src, sz, &ps);
//     if (ret == (size_t)(-1))
//     {
//         perror("wcsrtombs:");

//         delete[] tmp_name;
//         throw runtime_error("Cannot convert wchar");
//     }

//     delete[] tmp_name;
// }

//void charToWchar(wchar_t** dest, const char* data)
//{
//     mbstate_t ps;

//     const char** src = &data;

//     size_t sz = strlen(data) * sizeof(wchar_t) + sizeof(wchar_t);

//     cerr << "Try allocate " << sz << " bytes" << endl;

//     wchar_t* tmp_name = new wchar_t[sz];

//     size_t ret = wcsrtombs(tmp_name, src, sz, &ps);
//     if (ret == (size_t)(-1))
//     {
//         perror("wcsrtombs:");

//         wcerr << *src << endl;

//         delete[] tmp_name;
//         throw runtime_error("Cannot convert wchar");
//     }

//     delete[] tmp_name;
//}


// TextReader::TextReader(const std::string& file)
//     : text()
// {
//     text.open(file.c_str(), ios::binary | ios::in);

//     if (!text)
//         throw runtime_error("Cannot open file ");
// }

// bool TextReader::getLine(std::string& to)
// {
//     getline(text, to);

//     return text.good();
// }
